#ifndef THREADSAFEQUEUE_H
#define THREADSAFEQUEUE_H

#include <mutex>
#include <condition_variable>
#include <queue>
#include <atomic>

template<class T>

class ThreadSafeQueue
{
private:
	mutable std::mutex mut;
	std::queue<std::shared_ptr<T>> data_queue;
	std::condition_variable data_con;
	const int max_len;

public:
	ThreadSafeQueue(int max_len):max_len(max_len){}
	ThreadSafeQueue(ThreadSafeQueue const& other)
	{
		std::lock_guard<std::mutex> lk(other.mut);
		data_queue = other.data_queue;
	}
	~ThreadSafeQueue()
	{
		std::lock_guard<std::mutex> lk(mut);
		data_con.notify_all();
	}
	bool push(T tValue)
	{
		if (data_queue.size()> max_len-1)  return false;
		std::shared_ptr<T> data(std::make_shared<T>(std::move(tValue)));
		std::lock_guard<std::mutex> lk(mut);
		data_queue.push(data);
		data_con.notify_one();
		return true;
	}

	bool wait_and_pop(T& tValue)
	{
		std::unique_lock<std::mutex> lk(mut);
		data_con.wait(lk,[this]{return !data_queue.empty();});
		if (data_queue.empty()) return false;
		tValue = std::move(*data_queue.front());
		data_queue.pop();
		return true;
	}
	
	bool try_pop(T& tValue)
	{
		std::lock_guard<std::mutex> lk(mut);
		if(data_queue.empty())
			return false;
		tValue = std::move(*data_queue.front());
		data_queue.pop();
		return true;
	}

	// std::shared_ptr<T> try_pop()
	// {
	// 	std::lock_guard<std::mutex> lk(mut);
	// 	if(data_queue.empty())
	// 		return std::shared_ptr<T>();
	// 	std::shared_ptr<T> ret(std::make_shared(data_queue.front()));
	// 	data_queue.pop();
	// 	return ret;
	// }
	// std::shared_ptr<T>wait_and_pop()
	// {
	// 	std::unique_lock<std::mutex> lk(mut);
	// 	data_con.wait(lk,[this]{return !data_queue.empty();});
	// 	if(data_queue.empty()) return std::shared_ptr<T>();
	// 	std::shared_ptr<T> ret (std::make_shared<T>(data_queue.front()));
	// 	data_queue.pop();
	// 	return ret;
	// }

	bool empty() const
	{
		std::lock_guard<std::mutex> lk(mut);
		return data_queue.empty();
	}
};

#endif 